﻿using System;
using System.Collections.Generic;
using System.Threading;
using RockCorn.Objects;
using RockCorn.Objects.Moving;
using RockCorn.Interfaces;

namespace RockCorn.Internals
{
    public class Engine
    {
        protected IRenderer renderer;
        protected IUserInterface userInterface;
        protected List<GameObject> allObjects;
        protected List<MovingObject> movingObjects;
        protected List<GameObject> staticObjects;
        protected List<GameObject> objectsBlockingLevelComplete;
        protected Racket playerRacket;
        protected int sleepTime;

        protected static int defaultSleepTime = 200;

        //protected void FinishLevel()
        //{
        //    this.objectsBlockingLevelComplete.Clear();
        //}

        protected bool GameOver = false;
        protected bool LevelComplete = false;

        protected int score;

        public int Score { get { return this.score; } }

        public int SleepTime
        {
            get { return this.sleepTime; }
            private set
            {
                if (value > 0)
                    this.sleepTime = value;
                else
                    throw new ArgumentOutOfRangeException("Engine sleep time", "Sleep time should be a positive integer number (ms).");
            }
        }

        public Engine(IRenderer renderer, IUserInterface userInterface, int sleepTime, int score)
        {
            this.renderer = renderer;
            this.userInterface = userInterface;
            this.allObjects = new List<GameObject>();
            this.movingObjects = new List<MovingObject>();
            this.staticObjects = new List<GameObject>();
            this.objectsBlockingLevelComplete = new List<GameObject>();
            this.SleepTime = sleepTime;
            this.score = score;
        }

        public Engine(IRenderer renderer, IUserInterface userInterface, int score)
            : this(renderer, userInterface, defaultSleepTime, score)
        {
        }

        private void AddStaticObject(GameObject obj)
        {
            this.staticObjects.Add(obj);
            this.allObjects.Add(obj);
            if (!(obj is INonDestructable)) this.objectsBlockingLevelComplete.Add(obj);
        }

        private void AddMovingObject(MovingObject obj)
        {
            this.movingObjects.Add(obj);
            this.allObjects.Add(obj);
        }

        public virtual void AddObject(GameObject obj)
        {
            if (obj is MovingObject)
            {
                this.AddMovingObject(obj as MovingObject);
            }
            else
            {
                if (obj is Racket)
                {
                    AddRacket(obj as Racket);
                }
                else
                {
                    this.AddStaticObject(obj);
                }
            }
        }

        private void AddRacket(Racket obj)
        {
            // IsDestroyed can't be accessed!
            //this.allObjects[this.allObjects.IndexOf(this.playerRacket)].IsDestroyed = true;
            //this.staticObjects[this.staticObjects.IndexOf(this.playerRacket)].IsDestroyed = true;
            this.allObjects.Remove(this.playerRacket);
            this.staticObjects.Remove(this.playerRacket);

            // No racket drawn until next cycle!
            this.playerRacket = obj;
            this.AddStaticObject(obj);
        }

        public virtual void MovePlayerRacketLeft()
        {
            if(this.playerRacket.TopLeft.Col>0)
            this.playerRacket.MoveLeft();
        }

        public virtual void MovePlayerRacketRight()
        {
            if (this.playerRacket.TopLeft.Col+this.playerRacket.Width < ((renderer as ConsoleRenderer).RenderContextMatrixCols))
            this.playerRacket.MoveRight();
        }

        private void InputLoop()
        {
            while (true)
            {
                this.userInterface.ProcessInput();
            }
        }

        protected virtual void CheckGameOver()
        {
            if (this.movingObjects.Count == 0)
            {
                GameOver = true;
            }
        }

        protected virtual void CheckLevelComplete()
        {
            if (this.objectsBlockingLevelComplete.Count == 0)
            {
                if (this.movingObjects.Count == this.movingObjects.FindAll(obj => obj.GetCollisionGroupString().Contains("ball")).Count)
                {
                    LevelComplete = true;
                }
            }
        }

        public virtual void Run()
        {
            //int counter = 0;
            ThreadStart threadDelegate = new ThreadStart(this.InputLoop);
            Thread ReadInput = new Thread(threadDelegate);
            ReadInput.Start();

            this.renderer.RenderAll();

            while (true)
            {
                //counter++;
                //if (counter == 50)
                //{
                //    this.FinishLevel();
                //}

                System.Threading.Thread.Sleep(sleepTime);

                this.renderer.ClearQueue();

                for (int i=0; i<allObjects.Count; i++)
                {
                    allObjects[i].Update();

                    if((allObjects[i].GetTopLeft().Col<0)||(allObjects[i].GetTopLeft().Col>(this.renderer as ConsoleRenderer).RenderContextMatrixCols))
                        allObjects[i].IsDestroyed = true;
                    if ((allObjects[i].GetTopLeft().Row < 0) || (allObjects[i].GetTopLeft().Row > (this.renderer as ConsoleRenderer).RenderContextMatrixRows))
                        allObjects[i].IsDestroyed = true;

                    this.renderer.EnqueueForRendering(allObjects[i]);
                }

                CollisionDispatcher.HandleCollisions(this.movingObjects, this.staticObjects);

                List<GameObject> producedObjects = new List<GameObject>();

                foreach (var obj in this.allObjects)
                {
                    producedObjects.AddRange(obj.ProduceObjects());
                    if (obj.IsDestroyed)
                    {
                        score += obj.GetPoints();
                    }
                }

                this.allObjects.RemoveAll(obj => obj.IsDestroyed);
                this.movingObjects.RemoveAll(obj => obj.IsDestroyed);
                this.staticObjects.RemoveAll(obj => obj.IsDestroyed);
                this.objectsBlockingLevelComplete.RemoveAll(obj => obj.IsDestroyed);

                foreach (var obj in producedObjects)
                {
                    this.AddObject(obj);
                }

                this.renderer.RenderAll();

                CheckGameOver();
                CheckLevelComplete();
                if (GameOver)
                {
                    Console.Clear();
                    Console.WriteLine("GAME OVER! Score: {0}\nPress ENTER to exit.", this.score);
                    Console.ReadLine(); // clear leftovers
                    Console.ReadLine();
                    ReadInput.Abort();
                    Environment.Exit(0);
                }
                else if (LevelComplete)
                {
                    Console.Clear();
                    Console.WriteLine("Level complete! Score: {0}\nNext level starting in 10 seconds.", this.score);
                    Thread.Sleep(10000);
                    ReadInput.Abort();
                    break;
                }
            }
        }
    }
}
